mod defines;
mod utils;
mod log;
mod config;
mod game;
mod engine;
mod engine_ai;
mod learn;
mod protocol;
mod server;
mod lock_free;

use std::{
    env, io::{self, Write}, fs, thread, sync::{atomic::*, Arc},
};

use crate::{
    config::Config, defines::*, utils::lock_free_dt::IndexedList,
    learn::{WholeFrame,Rollout},
};

const WHOLE_USAGE:&str = r#"whole [options] <way>

options:
    -bin <path>
        use <path> file as whole-bin, create if not exist.
        (default whole.bin)

ways:
    view
        display whole-bin information.
    learn [-to N] [-a N] [-o out-path]
        learn whole-bin, add N times, until N total-times.
        output to out-path, overwrite if no set.
    final <path>
        make final version whole-bin to <path>, clear leran data.
    recover [path]
        make default version whole-bin, output to path(default whole.bin).

    enter REPL if no set <way>
"#;
fn whole_view_n(frame:&WholeFrame) {
    let n = frame.times();
    let nw_black = frame.nw_black();
    let nw_white = frame.nw_white();
    let black_rate = nw_black as f32 / n as f32;
    let white_rate = nw_white as f32 / n as f32;
    println!("All = {}", n);
    println!("Black-Win = {} ({:.3} / {}%)", nw_black, black_rate, (black_rate * 100.0) as i32);
    println!("White-Win = {} ({:.3} / {}%)", nw_white, white_rate, (white_rate * 100.0) as i32);
    println!("Draw = {}", n - nw_black - nw_white);
}
fn whole_view_table(frame:&WholeFrame) {
    fn display(table:&[f32; game::BOARD_LEN], name:&str) {
        println!("{}[{:.8}]:", name, table.iter().sum::<f32>());
        println!("[");
        let mut i = 0;
        for _ in 0..game::BOARD_SIZE {
            print!("   ");
            for _ in 0..game::BOARD_SIZE {
                print!(" {:.8},", table[i]);
                i += 1;
            }
            println!();
        }
        println!("]");
    }
    display(&frame.table_black(), "black");
    display(&frame.table_white(), "white");
}
fn whole_view_status(frame:&WholeFrame) {
    println!("Status = {}", frame.status());
}
fn whole_open(path:&str) -> Result<WholeFrame> {
    let is_existed = fs::exists(path)? && fs::metadata(path)?.is_file();
    if is_existed {
        WholeFrame::load(path)
    } else {
        Ok(WholeFrame::new())
    }
}
fn whole_learn(bin_path:&str, args:&[String]) -> Result<()> {
    let mut max_times = LERAN_WHOLE_MAX_TIMES;
    let mut add_times = 0;
    let mut out_path = bin_path;
    let mut ai = 0;
    while ai < args.len() {
        match args[ai].as_str() {
            "-to" => {
                if args.len() > 1+ai {
                    max_times = args[1+ai].parse::<usize>().unwrap_or(LERAN_WHOLE_MAX_TIMES);
                    max_times = max_times.min(LERAN_WHOLE_MAX_TIMES);
                    ai += 2;
                } else {break;}
            }
            "-a" => {
                if args.len() > 1+ai {
                    add_times = args[1+ai].parse::<usize>().unwrap_or(LERAN_WHOLE_MAX_TIMES);
                    add_times = add_times.min(LERAN_WHOLE_MAX_TIMES);
                    ai += 2;
                } else {break;}
            }
            "-o" => {
                if args.len() > 1+ai {
                    out_path = &args[1+ai];
                    ai += 2;
                }
            }
            _ => {break;}
        }
    }
    let mut frame = whole_open(bin_path)?;
    if frame.status().learn_able() {
        let times_before = frame.line_count();
        while add_times > 0 && frame.line_count() < max_times {
            frame.frame();
            add_times -= 1;
        }
        if frame.line_count() > times_before {
            frame.save(out_path)?;
        }
        Ok(())
    } else {
        Err(anyhow!("whole-bin '{}' not be learn-able status", bin_path))
    }
}
fn whole_final(bin_path:&str, args:&[String]) -> Result<()> {
    if args.is_empty() {
        Err(anyhow!("whole final: must have output path"))
    } else {
        let out_path = &args[0];
        if let Ok(p) = fs::canonicalize(out_path) {
            if fs::canonicalize(bin_path)? == p {
                return Err(anyhow!("whole final: can't overwrite bin path"));
            }
        }
        let mut frame = whole_open(bin_path)?;
        frame.to_final();
        frame.save(out_path)
    }
}
fn whole_recover(args:&[String]) -> Result<()> {
    let out_path = if args.is_empty() {PATH_WHOLE_BIN} else {&args[0]};
    let frame = WholeFrame::default();
    frame.save(out_path)
}
fn whole_repl(bin_path:&str) -> Result<()> {
    fn repl(frame:&mut WholeFrame, path:&str, running:&AtomicBool) -> Result<()> {
        print!("{path}> "); io::stdout().flush()?;
        for line in io::stdin().lines() {
            if let Ok(line) = line {
                match line.as_str() {
                    "v" => {whole_view_table(&frame);}
                    "n" => {whole_view_n(&frame);}
                    "s" => {whole_view_status(&frame);}
                    "w" => {frame.save(path)?;}
                    "wq" => {frame.save(path)?; break;}
                    "q" => {break;}
                    _ => {}
                }
                print!("{path}> "); io::stdout().flush()?;
            } else {break;}
        }
        running.store(false, Ordering::Relaxed);
        Ok(())
    }
    let running = Arc::new(AtomicBool::new(true));
    let frame_slot = IndexedList::new(1);
    let _ = frame_slot.set(0, whole_open(bin_path)?);
    if frame_slot.get_ref(0).unwrap().status().is_final() {
        println!("bin has Final");
        repl(frame_slot.get_mut(0).unwrap(), bin_path, &running)
    } else {
        let fuck_bow1 = frame_slot.get_mut(0).unwrap();
        let fuck_bow2 = running.clone();
        let handle = thread::spawn(move || {
            while fuck_bow2.load(Ordering::Relaxed) && fuck_bow1.line_count() < LERAN_WHOLE_MAX_TIMES {
                fuck_bow1.frame();
            }
        });
        let e = repl(frame_slot.get_mut(0).unwrap(), bin_path, &running);
        handle.join().unwrap();
        e
    }
}
fn subcommand_whole(args:&[String]) -> Result<()> {
    let mut bin_path:&str= PATH_WHOLE_BIN;
    let mut wi = 0;
    while wi < args.len() {
        match args[wi].as_str() {
            "-bin" => {
                if args.len() > 1+wi {
                    bin_path = args[1+wi].as_str();
                    if fs::exists(bin_path)? && (! fs::metadata(bin_path)?.is_file()) {
                        return Err(anyhow!("bin path must be file '{}'", bin_path));
                    } else {
                        wi += 2;
                    }
                }
            }
            _ => {break}
        }
    }
    let args = &args[wi..];
    if args.is_empty() {
        whole_repl(bin_path)
    } else {
        let head = args[0].to_ascii_lowercase();
        match head.as_str() {
            "view" => {
                let frame = learn::WholeFrame::load(bin_path)?;
                whole_view_status(&frame);
                whole_view_n(&frame);
                whole_view_table(&frame);
                Ok(())
            }
            "lws" => {
                let frame = learn::WholeFrame::load(bin_path)?;
                println!("black:\n{:?}", frame.lw_blacks());
                println!("white:\n{:?}", frame.lw_whites());
                Ok(())
            }
            "learn" => {
                whole_learn(bin_path, &args[1..])
            }
            "final" => {
                whole_final(bin_path, &args[1..])
            }
            "recover" => {
                whole_recover(&args[1..])
            }
            _ => {
                println!("{}", WHOLE_USAGE); Ok(())
            }
        }
    }
}
fn rollout_repl(bin_path:&str) -> Result<()> {
    fn repl(frame:&mut Rollout, path:&str, running:&AtomicBool) -> Result<()> {
        print!("{path}> "); io::stdout().flush()?;
        for line in io::stdin().lines() {
            if let Ok(line) = line {
                match line.as_str() {
                    "v" => {}
                    "n" => {}
                    "s" => {}
                    //"w" => {frame.save(path)?;}
                    //"wq" => {frame.save(path)?; break;}
                    "q" => {break;}
                    _ => {}
                }
                print!("{path}> "); io::stdout().flush()?;
            } else {break;}
        }
        running.store(false, Ordering::Relaxed);
        Ok(())
    }
    let running = Arc::new(AtomicBool::new(true));
    let frame_slot = IndexedList::new(1);
    let _ = frame_slot.set(0, Rollout::new());
    let fuck_bow1 = frame_slot.get_mut(0).unwrap();
    let fuck_bow2 = running.clone();
    let handle = thread::spawn(move || {
        while fuck_bow2.load(Ordering::Relaxed) {
            fuck_bow1.frame();
        }
    });
    let e = repl(frame_slot.get_mut(0).unwrap(), bin_path, &running);
    handle.join().unwrap();
    e
}
fn subcommand_rollout(args:&[String]) -> Result<()> {
    rollout_repl("TEST")
}
fn subcommand_make_table(args:&[String]) -> Result<()> {
    let path_line_bits = if args.len() > 0 {&args[0]} else {"./line-bits.txt"};
    let path_states = if args.len() > 1 {&args[1]} else {"./states.txt"};
    game::make_line_bits(&path_line_bits)?;
    println!("line-bits -> {}", path_line_bits);
    game::make_states(&path_states)?;
    println!("states -> {}", path_states);
    Ok(())
}
fn do_args(args:&[String]) -> Result<()> {
    debug_assert!(! args.is_empty());
    log::debug!("shell {:?}", args);
    let head = args[0].to_ascii_lowercase();
    match head.as_str() {
        "-force-quit" => {server::force_quit()?;}
        "-version" | "-v" => {println!{"{}", VERSION};}
        "-make-table" => {subcommand_make_table(&args[1..])?}
        "-whole" => {subcommand_whole(&args[1..])?;}
        "-rollout" => {subcommand_rollout(&args[1..])?;}
        _ => {println!("unknown command: {:?}", args)}
    }
    Ok(())
}

fn main() {
    if let Err(e) = log::_init() {
        println!("log '{}' initialize failure: {}", PATH_LOG, e);
    } else {
        let config = Config::load(PATH_CONFIG);
        let args:Vec<String> = env::args().skip(1).collect();
        if args.is_empty() {
            match engine::_init(&config) {
                Ok(()) => {
                    if let Err(e) = protocol::_init(&config) {
                        println!("error: {e}");
                    } else {
                        if let Err(e) = server::start(&config) {
                            println!("error: {e}");
                        }
                        protocol::_raii();
                    }
                    engine::_raii();
                }
                Err(e) => {
                    println!("error: {e}")
                }
            }
        } else if let Err(e) = do_args(&args) {
            println!("error: {e}");
        }
    }
}
